"""
This module implements an XML comparer

>>> from lxml import etree
>>> xml1 = "<a><b/><c>text</c><d/></a>"
>>> doc1 = etree.fromstring(xml1)
>>> xml2 = "<a><b/><c>text</c><d/></a>"
>>> doc2 = etree.fromstring(xml2)
>>> xml_compare(doc1,doc2)
True
>>> xml3 = "<a><b/><c>texto</c><d/></a>"
>>> doc3 = etree.fromstring(xml3)
>>> xml_compare(doc1,doc3)
False
>>> xml4 = "<a><b/><c> text </c><d/></a>"
>>> doc4 = etree.fromstring(xml4)
>>> xml_compare(doc1,doc4,reporter=sys.stderr.write,strip_whitespaces=True)
True
"""
import sys
import os

def getNodePath(node):
    return node.getroottree().getpath(node)

def doStripWhitespaces(text):
    if text==None:
        return None
    else:
        return text.strip().replace('\n','').replace('\r','')

def text_compare(t1, t2,strip_whitespaces=False):
    """ Text comparer for XML Text Nodes
    >>> text_compare("hi","hi")
    True
    >>> text_compare("hi","hi ")
    False
    >>> text_compare("hi","hi ", strip_whitespaces=True)
    True
    """
    if not t1 and not t2:
        return True
    if t1 == '*' or t2 == '*':
        return True
    if strip_whitespaces:
        return (doStripWhitespaces(t1) or '') == (doStripWhitespaces(t2) or '')
    else:
        return (t1 or '') == (t2 or '')

def doReport(reporter,x1,x2,errorMsg):
    if reporter:
        reporter(getNodePath(x1)+" "+getNodePath(x2)+os.linesep+errorMsg+os.linesep)
    
def xml_compare(x1, x2, reporter=None, strip_whitespaces=False,ignore_order=False):
    if x1.tag != x2.tag:
        doReport(reporter,x1,x2,'Tags do not match: %s and %s' % (x1.tag, x2.tag))
        return False
                 
    for name, value in x1.attrib.items():
        if x2.attrib.get(name) != value:
            doReport(reporter,x1,x2,'Attributes do not match: %s=%r, %s=%r'
                         % (name, value, name, x2.attrib.get(name)))
            return False
    for name in x2.attrib.keys():
        if not x1.attrib.has_key(name):
            doReport(reporter,x1,x2,'x2 has an attribute x1 is missing: %s' % name)
            return False
    if not text_compare(x1.text, x2.text, strip_whitespaces):
        doReport(reporter,x1,x2,'text: %r != %r' % (x1.text, x2.text))
        return False

    if not text_compare(x1.tail, x2.tail, strip_whitespaces):
        doReport(reporter,x1,x2,'tail: %r != %r' % (x1.tail, x2.tail))
        return False
    
    cl1 = x1.getchildren()
    cl2 = x2.getchildren()
    if len(cl1) != len(cl2):
        doReport(reporter,x1,x2,'children length differs, %i != %i'% (len(cl1), len(cl2)))
        return False
    i = 0
    for c1, c2 in zip(cl1, cl2):
        i += 1
        if not xml_compare(c1, c2, reporter, strip_whitespaces):
            doReport(reporter,c1,c2,'children %i do not match: %s'% (i, c1.tag))
            return False
    return True


def _test():
    import doctest
    doctest.testmod()

if __name__ == "__main__":
    _test()

